<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JSON Schema Builder</title>
  <style>
    * {
      box-sizing: border-box;
    }
    
    body {
      font-family: Helvetica, Arial, sans-serif;
      margin: 0;
      padding: 20px;
      background-color: #f5f5f5;
    }
    
    h1, h2, h3 {
      font-weight: 500;
    }
    
    .container {
      max-width: 100%;
      margin: 0 auto;
      background-color: white;
      border-radius: 8px;
      box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
      padding: 20px;
    }
    
    .schema-builder {
      display: flex;
      flex-direction: column;
      gap: 20px;
    }
    
    .editor-panel {
      width: 100%;
      padding: 15px;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
    
    .output-panel {
      width: 100%;
      position: relative;
    }
    
    .schema-output {
      width: 100%;
      height: 300px;
      background-color: #f8f8f8;
      border: 1px solid #ddd;
      border-radius: 4px;
      padding: 15px;
      font-family: monospace;
      overflow-y: auto;
      white-space: pre-wrap;
    }
    
    .property-list {
      margin-top: 20px;
    }
    
    .property-item {
      border: 1px solid #ddd;
      border-radius: 4px;
      padding: 15px;
      margin-bottom: 15px;
      background-color: #f9f9f9;
    }
    
    .nested-properties {
      margin-left: 20px;
      margin-top: 15px;
      padding-left: 10px;
      border-left: 2px solid #2196F3;
    }
    
    .actions {
      margin-top: 15px;
      display: flex;
      gap: 10px;
    }
    
    button {
      padding: 8px 12px;
      background-color: #2196F3;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 14px;
    }
    
    button:hover {
      background-color: #0b7dda;
    }
    
    button.secondary {
      background-color: #f1f1f1;
      color: #333;
      border: 1px solid #ddd;
    }
    
    button.secondary:hover {
      background-color: #e1e1e1;
    }
    
    button.remove {
      background-color: #f44336;
    }
    
    button.remove:hover {
      background-color: #d32f2f;
    }
    
    button.copy {
      position: absolute;
      top: 10px;
      right: 10px;
      z-index: 10;
    }
    
    .form-group {
      margin-bottom: 15px;
    }
    
    label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
    }
    
    input, select, textarea {
      width: 100%;
      padding: 8px;
      border: 1px solid #ddd;
      border-radius: 4px;
      font-size: 16px;
      font-family: Helvetica, Arial, sans-serif;
    }
    
    .checkbox-group {
      display: flex;
      align-items: center;
      gap: 5px;
    }
    
    .checkbox-group input {
      width: auto;
    }
    
    .alert {
      padding: 10px 15px;
      background-color: #f8d7da;
      color: #721c24;
      border: 1px solid #f5c6cb;
      border-radius: 4px;
      margin-bottom: 15px;
      display: none;
    }
    
    .success {
      background-color: #d4edda;
      color: #155724;
      border-color: #c3e6cb;
    }
    
    @media (min-width: 768px) {
      .schema-output {
        height: 400px;
      }
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>JSON Schema Builder</h1>
    <p>Build your JSON schema by adding properties and nested objects.</p>
    
    <div id="alert" class="alert"></div>
    
    <div class="schema-builder">
      <div class="editor-panel">
        <h2>Schema properties</h2>
        
        <div class="form-group">
          <label for="schema-title">Schema title</label>
          <input type="text" id="schema-title" placeholder="Enter schema title">
        </div>
        
        <div class="form-group">
          <label for="schema-description">Schema description</label>
          <textarea id="schema-description" rows="2" placeholder="Enter schema description"></textarea>
        </div>
        
        <div class="property-list" id="property-list">
          <!-- Property items will be added here -->
        </div>
        
        <button id="add-property">Add property</button>
      </div>
      
      <div class="output-panel">
        <h2>Schema output</h2>
        <button id="copy-button" class="copy">Copy</button>
        <div id="schema-output" class="schema-output"></div>
      </div>
    </div>
  </div>
  
  <script type="module">
// Main schema object
const schema = {
  type: "object",
  properties: {}
};

// DOM elements
const schemaTitle = document.getElementById("schema-title");
const schemaDescription = document.getElementById("schema-description");
const propertyList = document.getElementById("property-list");
const addPropertyButton = document.getElementById("add-property");
const schemaOutput = document.getElementById("schema-output");
const copyButton = document.getElementById("copy-button");
const alertElement = document.getElementById("alert");

// Event listeners
schemaTitle.addEventListener("input", updateSchema);
schemaDescription.addEventListener("input", updateSchema);
addPropertyButton.addEventListener("click", addProperty);
copyButton.addEventListener("click", copySchema);

// Initialize
loadFromHash();
window.addEventListener("hashchange", loadFromHash);

// Add a new property
function addProperty(event, parentElement = propertyList, parentPath = null) {
  const propertyItem = document.createElement("div");
  propertyItem.className = "property-item";
  
  const propertyId = "prop-" + Date.now() + "-" + Math.floor(Math.random() * 1000);
  propertyItem.dataset.id = propertyId;
  
  propertyItem.innerHTML = `
    <div class="form-group">
      <label for="${propertyId}-name">Property name</label>
      <input type="text" id="${propertyId}-name" class="property-name" placeholder="Enter property name">
    </div>
    
    <div class="form-group">
      <label for="${propertyId}-type">Property type</label>
      <select id="${propertyId}-type" class="property-type">
        <option value="string">String</option>
        <option value="number">Number</option>
        <option value="integer">Integer</option>
        <option value="boolean">Boolean</option>
        <option value="object">Object</option>
        <option value="array">Array</option>
      </select>
    </div>
    
    <div class="form-group">
      <label for="${propertyId}-description">Description</label>
      <input type="text" id="${propertyId}-description" class="property-description" placeholder="Property description">
    </div>
    
    <div class="form-group array-item-type-container" style="display: none;">
      <label for="${propertyId}-array-type">Array items type</label>
      <select id="${propertyId}-array-type" class="array-item-type">
        <option value="string">String</option>
        <option value="number">Number</option>
        <option value="integer">Integer</option>
        <option value="boolean">Boolean</option>
        <option value="object">Object</option>
      </select>
    </div>
    
    <div class="form-group">
      <div class="checkbox-group">
        <input type="checkbox" id="${propertyId}-required" class="property-required">
        <label for="${propertyId}-required">Required</label>
      </div>
    </div>
    
    <div class="nested-properties" id="${propertyId}-nested" style="display: none;"></div>
    
    <div class="actions">
      <button class="add-nested" style="display: none;">Add nested property</button>
      <button class="remove">Remove</button>
    </div>
  `;
  
  parentElement.appendChild(propertyItem);
  
  // Event listeners for the new property
  const typeSelect = propertyItem.querySelector(".property-type");
  const arrayTypeContainer = propertyItem.querySelector(".array-item-type-container");
  const arrayTypeSelect = propertyItem.querySelector(".array-item-type");
  const nestedContainer = propertyItem.querySelector(".nested-properties");
  const addNestedButton = propertyItem.querySelector(".add-nested");
  const removeButton = propertyItem.querySelector(".remove");
  
  const nameInput = propertyItem.querySelector(".property-name");
  const descriptionInput = propertyItem.querySelector(".property-description");
  const requiredCheckbox = propertyItem.querySelector(".property-required");
  
  // Handle property type change
  typeSelect.addEventListener("change", function() {
    const isObject = this.value === "object";
    const isArray = this.value === "array";
    
    nestedContainer.style.display = isObject ? "block" : "none";
    addNestedButton.style.display = isObject ? "inline-block" : "none";
    arrayTypeContainer.style.display = isArray ? "block" : "none";
    
    updateSchema();
  });
  
  // Handle array item type change
  arrayTypeSelect.addEventListener("change", function() {
    const isObject = this.value === "object";
    
    if (isObject && typeSelect.value === "array") {
      nestedContainer.style.display = "block";
      addNestedButton.style.display = "inline-block";
    } else {
      nestedContainer.style.display = "none";
      addNestedButton.style.display = "none";
    }
    
    updateSchema();
  });
  
  // Add nested property
  addNestedButton.addEventListener("click", function(event) {
    event.preventDefault();
    addProperty(event, nestedContainer, propertyId);
  });
  
  // Remove property
  removeButton.addEventListener("click", function() {
    propertyItem.remove();
    updateSchema();
  });
  
  // Update schema on input change
  nameInput.addEventListener("input", updateSchema);
  descriptionInput.addEventListener("input", updateSchema);
  requiredCheckbox.addEventListener("change", updateSchema);
  
  updateSchema();
  return propertyItem;
}

// Update schema JSON based on form inputs
function updateSchema() {
  // Update title and description
  if (schemaTitle.value) {
    schema.title = schemaTitle.value;
  } else {
    delete schema.title;
  }
  
  if (schemaDescription.value) {
    schema.description = schemaDescription.value;
  } else {
    delete schema.description;
  }
  
  // Reset properties and required
  schema.properties = {};
  schema.required = [];
  
  // Process all property items
  processPropertyItems(propertyList.children, schema.properties, schema.required);
  
  // Remove required if empty
  if (schema.required.length === 0) {
    delete schema.required;
  }
  
  // Update the output
  schemaOutput.textContent = JSON.stringify(schema, null, 2);
  
  // Update URL hash
  updateUrlHash();
}

// Process property items recursively
function processPropertyItems(items, propertiesObj, requiredArr) {
  Array.from(items).forEach(item => {
    if (!item.classList.contains("property-item")) return;
    
    const nameInput = item.querySelector(".property-name");
    const typeSelect = item.querySelector(".property-type");
    const descriptionInput = item.querySelector(".property-description");
    const requiredCheckbox = item.querySelector(".property-required");
    const arrayTypeSelect = item.querySelector(".array-item-type");
    const nestedContainer = item.querySelector(".nested-properties");
    
    const name = nameInput.value.trim();
    if (!name) return;
    
    const type = typeSelect.value;
    
    // Create property definition
    const property = {
      type: type
    };
    
    // Add description if provided
    if (descriptionInput.value.trim()) {
      property.description = descriptionInput.value.trim();
    }
    
    // Handle arrays
    if (type === "array") {
      const itemsType = arrayTypeSelect.value;
      
      if (itemsType === "object") {
        property.items = {
          type: "object",
          properties: {}
        };
        
        const nestedRequired = [];
        processPropertyItems(nestedContainer.children, property.items.properties, nestedRequired);
        
        if (nestedRequired.length > 0) {
          property.items.required = nestedRequired;
        }
      } else {
        property.items = { type: itemsType };
      }
    }
    
    // Handle objects
    if (type === "object") {
      property.properties = {};
      const nestedRequired = [];
      
      processPropertyItems(nestedContainer.children, property.properties, nestedRequired);
      
      if (nestedRequired.length > 0) {
        property.required = nestedRequired;
      }
    }
    
    // Add to properties
    propertiesObj[name] = property;
    
    // Add to required array if checked
    if (requiredCheckbox.checked) {
      requiredArr.push(name);
    }
  });
}

// Copy schema to clipboard
function copySchema() {
  const textarea = document.createElement("textarea");
  textarea.value = schemaOutput.textContent;
  document.body.appendChild(textarea);
  textarea.select();
  
  try {
    document.execCommand("copy");
    showAlert("Schema copied to clipboard!", true);
  } catch (err) {
    showAlert("Failed to copy schema", false);
  }
  
  document.body.removeChild(textarea);
}

// Show alert message
function showAlert(message, isSuccess = false) {
  alertElement.textContent = message;
  alertElement.style.display = "block";
  
  if (isSuccess) {
    alertElement.classList.add("success");
  } else {
    alertElement.classList.remove("success");
  }
  
  setTimeout(() => {
    alertElement.style.display = "none";
  }, 3000);
}

// Update URL hash with schema
function updateUrlHash() {
  try {
    const hashValue = btoa(JSON.stringify(schema));
    window.history.replaceState(null, null, "#" + hashValue);
  } catch (e) {
    console.error("Error updating URL hash:", e);
  }
}

// Load schema from URL hash
function loadFromHash() {
  if (!window.location.hash) return;
  
  try {
    const hash = window.location.hash.substring(1);
    const decodedSchema = JSON.parse(atob(hash));
    
    // Reset form
    propertyList.innerHTML = "";
    
    // Set title and description
    if (decodedSchema.title) {
      schemaTitle.value = decodedSchema.title;
    } else {
      schemaTitle.value = "";
    }
    
    if (decodedSchema.description) {
      schemaDescription.value = decodedSchema.description;
    } else {
      schemaDescription.value = "";
    }
    
    // Rebuild properties
    if (decodedSchema.properties) {
      buildPropertiesFromSchema(decodedSchema.properties, propertyList, decodedSchema.required || []);
    }
    
    // Update output
    schema.title = decodedSchema.title;
    schema.description = decodedSchema.description;
    schema.properties = decodedSchema.properties || {};
    schema.required = decodedSchema.required || [];
    
    schemaOutput.textContent = JSON.stringify(schema, null, 2);
  } catch (e) {
    console.error("Error loading from hash:", e);
    showAlert("Failed to load schema from URL", false);
  }
}

// Build properties from loaded schema
function buildPropertiesFromSchema(properties, container, requiredArr) {
  for (const propName in properties) {
    const property = properties[propName];
    const propItem = addProperty(null, container);
    
    // Set basic property fields
    propItem.querySelector(".property-name").value = propName;
    propItem.querySelector(".property-type").value = property.type;
    
    if (property.description) {
      propItem.querySelector(".property-description").value = property.description;
    }
    
    if (requiredArr.includes(propName)) {
      propItem.querySelector(".property-required").checked = true;
    }
    
    // Handle arrays
    if (property.type === "array" && property.items) {
      const arrayTypeContainer = propItem.querySelector(".array-item-type-container");
      const arrayTypeSelect = propItem.querySelector(".array-item-type");
      const nestedContainer = propItem.querySelector(".nested-properties");
      const addNestedButton = propItem.querySelector(".add-nested");
      
      arrayTypeContainer.style.display = "block";
      
      if (property.items.type) {
        arrayTypeSelect.value = property.items.type;
        
        if (property.items.type === "object" && property.items.properties) {
          nestedContainer.style.display = "block";
          addNestedButton.style.display = "inline-block";
          
          buildPropertiesFromSchema(
            property.items.properties, 
            nestedContainer, 
            property.items.required || []
          );
        }
      }
    }
    
    // Handle objects
    if (property.type === "object" && property.properties) {
      const nestedContainer = propItem.querySelector(".nested-properties");
      const addNestedButton = propItem.querySelector(".add-nested");
      
      nestedContainer.style.display = "block";
      addNestedButton.style.display = "inline-block";
      
      buildPropertiesFromSchema(
        property.properties, 
        nestedContainer, 
        property.required || []
      );
    }
  }
}
  </script>
</body>
</html>
